#include <stdlib.h> /* for exit() */
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h> /* memset() */
#include <sys/time.h> /* select() */
#include "consts.h"

#define SOCKET_ERROR -1

int isReadable(int sd,int timeOut) { // milliseconds
  fd_set socketReadSet;
  FD_ZERO(&socketReadSet);
  FD_SET(sd,&socketReadSet);
  struct timeval tv;
  tv.tv_sec  = timeOut / 1000;
  tv.tv_usec = (timeOut % 1000) * 1000;
  if (select(sd+1,&socketReadSet,0,0,&tv) == SOCKET_ERROR) {
    return 0;
  } // if
  return FD_ISSET(sd,&socketReadSet) != 0;
} /* isReadable */


#define USER_NAME_ARG 1
#define CLIENT_PORT_ARG 2
#define SERVER_NAME_ARG 3
#define SERVER_PORT_ARG 4
#define TIMEOUT_CMD 500


struct sockaddr_in cliCtrlAddr, cliMsgAddr, ctrlServAddr, msgServAddr;
char msg[MAX_MSG_LEN] = {0};
char* UserName;
int ClientPort;
char* ServerName;
int ServerPort;
int rc, ctrlSocket, msgSocket;

int recieveCmdResult(char* failMsg)
{
	int n,echoLen;
	if (!isReadable(ctrlSocket,TIMEOUT_CMD))
	{
	  printf("FAIL: No response from server.\n");
	  return 0;
	}
	echoLen = sizeof(ctrlServAddr);
	memset(msg,0x0,MAX_MSG_LEN);
	n = recvfrom(ctrlSocket, msg, MAX_MSG_LEN, 0,
		  (struct sockaddr *) &ctrlServAddr, &echoLen);
	if(n<0)
	{
	  printf("%s: cannot receive data \n",UserName);
	  return 0;
	}

	/* print received message */

	if (!strncmp(msg,RES_FAIL,strlen(RES_FAIL)))
	{
		printf("%s\n",failMsg);
		return 0;
	}
	else if (strncmp(msg,RES_OK,strlen(RES_OK)))
	{
		printf("%s\n",msg);
	}
	return 1;
}

void executeExit()
{
	char command[MAX_MSG_LEN] = {0};
	sprintf(command,"%s %s",UserName,CMD_EXIT);
	rc = sendto(ctrlSocket, command, strlen(command)+1, 0,
			(struct sockaddr *) &ctrlServAddr,
			sizeof(ctrlServAddr));

	recieveCmdResult("FAIL: exit failed.\n");

	printf("Bye!\n");
	exit(0);

}

void requestGroup()
{
	char command[MAX_MSG_LEN] = {0};
	sprintf(command,"%s %s",UserName,CMD_GROUPS);
	rc = sendto(ctrlSocket, command, strlen(command)+1, 0,
			(struct sockaddr *) &ctrlServAddr,
			sizeof(ctrlServAddr));
}

void executeGroups()
{
	requestGroup();

	recieveCmdResult("FAIL: showing groups failed.\n");

}

void requestUsers(char * group)
{
	char command[MAX_MSG_LEN] = {0};
	sprintf(command,"%s %s",UserName,CMD_USERS);
	if (group)
	{
		strncat(command," ",1);
		strncat(command,group,strlen(group));
	}
	rc = sendto(ctrlSocket, command, strlen(command)+1, 0,
			(struct sockaddr *) &ctrlServAddr,
			sizeof(ctrlServAddr));
}
void executeUsers(char * group)
{
	requestUsers(group);

	recieveCmdResult("FAIL: exit failed.\n");

}

void executeSend(char * target, char * message)
{
	char command[MAX_MSG_LEN] = {0};
	if (!target || !strlen(target )
			|| !message || !strlen(message))
	{
		printf("--bad usage! usage: !send <target> <msg>\n");
		return;
	}
	sprintf(command,"%s %s %s %s",UserName,CMD_MESSAGE,target, message);
	rc = sendto(ctrlSocket, command, strlen(command)+1, 0,
			(struct sockaddr *) &ctrlServAddr,
			sizeof(ctrlServAddr));

	if(rc<0) {
	  printf("Cannot send message request.\n");
	}

	if(recieveCmdResult("FAIL: target doesn't exist or you're not in the requested group.\n"))
	{
		printf("Sent message to %s.\n", target);
	}

}

void executeJoin(char* group)
{
	char command[MAX_MSG_LEN] = {0};
	if (group == NULL)
	{
		printf("--bad usage! usage: join <group>\n");
		return;
	}
	sprintf(command,"%s %s %s",UserName,CMD_JOIN,group);
	rc = sendto(ctrlSocket, command, strlen(command)+1, 0,
			(struct sockaddr *) &ctrlServAddr,
			sizeof(ctrlServAddr));

	if(rc<0) {
	  printf("Cannot send join request\n");
	}

	if (recieveCmdResult("FAIL: max groups exceeded.\n"))
	{
		printf("%s joined the group.\n", UserName);
	}
}

void executeLeave(char* group)
{
	char command[MAX_MSG_LEN] = {0};
	if (group == NULL)
	{
		printf("--bad usage! usage: join <group>\n");
		return;
	}
	sprintf(command,"%s %s %s",UserName,CMD_LEAVE,group);
	rc = sendto(ctrlSocket, command, strlen(command)+1, 0,
			(struct sockaddr *) &ctrlServAddr,
			sizeof(ctrlServAddr));

	if(rc<0) {
	  printf("Cannot send Registration.");
	}

	if (recieveCmdResult("FAIL: can't leave group.\n"))
	{
		printf("%s left the group.\n", UserName);
	}

}

int getNextOutput(char * buff)
{
	int n,echoLen;
	if (!isReadable(ctrlSocket,TIMEOUT_CMD))
	{
	  printf("FAIL: No response from server.\n");
	  return 0;
	}
	echoLen = sizeof(ctrlServAddr);
	memset(buff,0x0,MAX_MSG_LEN);
	n = recvfrom(ctrlSocket, buff, MAX_MSG_LEN, 0,
		  (struct sockaddr *) &ctrlServAddr, &echoLen);
	if(n<0)
	{
	  printf("%s: cannot receive data \n",UserName);
	  return 0;
	}
	return 1;
}


void sendMessage(char * message)
{
	char buff[MAX_MSG_LEN] = {0};
	char buff_msg[MAX_MSG_LEN] = {0};
	char * group;
	char res[MAX_MSG_LEN * MAX_GROUPS + MAX_GROUPS] = {0};
	strncpy(buff_msg, message, strlen(message)+1);
	requestGroup();

	if (!getNextOutput(buff))
		return;
	if (!strlen(buff))
		printf("No groups in server.\n");
	group = strtok(buff," ");
	while(group)
	{

		char buff_users[MAX_MSG_LEN] = {0};

		requestUsers(group);

		if (!getNextOutput(buff_users))
			return;
		if (strstr(buff_users,UserName))
		{
			executeSend(group,buff_msg);
		}

		group = strtok(NULL," ");
	}
}

void executeMyGroups()
{
	char buff[MAX_MSG_LEN] = {0};
	char * group;
	char res[MAX_MSG_LEN * MAX_GROUPS + MAX_GROUPS] = {0};
	requestGroup();

	if (!getNextOutput(buff))
		return;
	if (!strlen(buff))
	{
		printf("No groups in server.\n");
		return;
	}
	group = strtok(buff," ");
	while(group)
	{
		char buff_users[MAX_MSG_LEN] = {0};

		requestUsers(group);

		if (!getNextOutput(buff_users))
			return;
		if (strstr(buff_users,UserName))
		{
			if (!strlen(res))
				strncpy(res,group,strlen(group)+1);
			else
			{
				strncat(res," ",1);
				strncat(res,group,strlen(group));
			}
		}

		group = strtok(NULL," ");
	}
	printf("%s %s\n",
			strlen(res) ? "Groups: " : "No groups for user - "
					,strlen(res) ? res : UserName);
}


void executeCommand(char* cmd)
{
	char * pch;
	pch = strtok (cmd," ");
	if (!strcmp(pch,CMD_JOIN))
	{
		executeJoin(strtok(NULL, " "));
	}
	else if (!strcmp(pch,CMD_LEAVE))
	{
		executeLeave(strtok (NULL, " "));
	}
	else if (!strcmp(pch,CMD_EXIT))
	{
		executeExit();
	}
	else if (!strcmp(pch,CMD_SEND))
	{
		char* target = strtok (NULL, " ");
		executeSend(target,target + strlen(target) + 1);
	}
	else if (!strcmp(pch,CMD_MYGROUPS))
	{
		executeMyGroups();
	}
	else if (!strcmp(pch,CMD_GROUPS))
	{
		executeGroups();
	}
	else if (!strcmp(pch,CMD_USERS))
	{
		executeUsers(strtok (NULL, " "));
	}
	else
	{
		printf("Invalid command\n");
	}
}

int registerToServer()
{
	char port[6] = {0};
	sprintf(port,"%d",ClientPort);
	strncpy(msg,UserName,strlen(UserName)+1);
	strncat(msg," ",1);
	strncat(msg,CMD_NEW,strlen(CMD_NEW));
	strncat(msg," ",1);
	strncat(msg,port,strlen(port));
	rc = sendto(ctrlSocket, msg, strlen(msg)+1, 0,
			(struct sockaddr *) &ctrlServAddr,
			sizeof(ctrlServAddr));

	if(rc<0) {
	  printf("Cannot send Registration.");
	  exit(1);
	}
	memset(msg,0x0,MAX_MSG_LEN);

	return recieveCmdResult("FAIL: can't register. max user exceeded or already registered.\n");
}

int main(int argc, char *argv[]) {

  int i, n, echoLen, flags, error;
  struct hostent *h;

  /* check command line args */
  if(argc!=5) {
    printf("Invalid arguments!\n");
    printf("usage : %s <user_name> <client_port> <server_name> <server_port>\n", argv[0]);
    exit(1);
  }

  UserName = argv[USER_NAME_ARG];
  ClientPort = atoi(argv[CLIENT_PORT_ARG]);
  ServerName = argv[SERVER_NAME_ARG];
  ServerPort = atoi(argv[SERVER_PORT_ARG]);


  /* get server IP address (no check if input is IP address or DNS name */
  h = gethostbyname(ServerName);
  if(h==NULL) {
    printf("Can't connect %s:%d!\n", ServerName, ServerPort);
    exit(1);
  }



  printf("Connecting to %s:%d (IP : %s)\n", ServerName, ServerPort, h->h_name,
	 inet_ntoa(*(struct in_addr *)h->h_addr_list[0]));


  ctrlServAddr.sin_family = h->h_addrtype;
  memcpy((char *) &ctrlServAddr.sin_addr.s_addr,
	 h->h_addr_list[0], h->h_length);
  ctrlServAddr.sin_port = htons(ServerPort);

  /* socket creation */
  ctrlSocket = socket(AF_INET,SOCK_DGRAM,0);
  if(ctrlSocket<0) {
    printf("Can't open socket\n");
    exit(1);
  }

  /* bind any port */
  cliCtrlAddr.sin_family = AF_INET;
  cliCtrlAddr.sin_addr.s_addr = htonl(INADDR_ANY);
  cliCtrlAddr.sin_port = htons(0);


  rc = bind(ctrlSocket, (struct sockaddr *) &cliCtrlAddr, sizeof(cliCtrlAddr));
  if(rc<0) {
    printf("Can't bind port\n");
    exit(1);
  }


  msgServAddr.sin_family = h->h_addrtype;
  memcpy((char *) &msgServAddr.sin_addr.s_addr,
	 h->h_addr_list[0], h->h_length);
  msgServAddr.sin_port = htons(ServerPort);


  /* socket creation */
  msgSocket = socket(AF_INET,SOCK_DGRAM,0);
  if(msgSocket<0) {
    printf("Can't open socket\n");
    exit(1);
  }

  /* bind any port */
  cliMsgAddr.sin_family = AF_INET;
  cliMsgAddr.sin_addr.s_addr = htonl(INADDR_ANY);
  cliMsgAddr.sin_port = htons(ClientPort);

  rc = bind(msgSocket, (struct sockaddr *) &cliMsgAddr, sizeof(cliMsgAddr));
  if(rc<0) {
    printf("Can't bind port\n");
    exit(1);
  }

  flags = 0;

  printf("Registering %s...\n",UserName);
  if (registerToServer())
  {
	  printf("Success.\n");
  }
  else
  {
	  exit(1);
  }

  while (1)
  {
	  fd_set rfds;

	  FD_ZERO(&rfds);
	  FD_SET(0, &rfds);
	  FD_SET(msgSocket, &rfds);


	  select( FD_SETSIZE, &rfds, NULL, NULL, NULL );


	  if ( FD_ISSET( 0, &rfds ) )
	  {

		memset(msg,0x0,MAX_MSG_LEN);

		if (scanf("%[^\n]%*c", msg) <= 0)
		{
		  continue;
		}

		if (msg[0] == '!')
		{
		  executeCommand(msg+1);
		}
		else
		{
		  sendMessage(msg);
		}
	  }

	  if ( FD_ISSET( msgSocket, &rfds ) )
	  {
	    char buffer[MAX_MSG_LEN];
	    echoLen = sizeof(msgServAddr);
	    int n = recvfrom(msgSocket, buffer, MAX_MSG_LEN, 0,
	  		  (struct sockaddr *) &msgServAddr, &echoLen);
	    if ( n < 1 )
	    {
	      perror( "read() failed" );
	    }
	    else
	    {
	      buffer[n] = '\0';
		  char* user = strtok(buffer, " ");
	      printf( "%s: %s\n", user, buffer + strlen(user)+1 );
	      fflush(stdout);
	    }
	  }


  }

  return 1;

}
